#include "StdAfx.hpp"
#include "IfsFractals.hpp"
#include "Framework.hpp"
#include "Gfx2D.hpp"
#include "D3dUtils.hpp"
#include "Font.hpp"
#include "AsyncConsole.hpp"
#include "VariableTypes.hpp"
#include "Gradients.hpp"

using namespace common;

namespace IfsFractal {

const char * START_TYPE_NAMES[] = {
	"Random",
	"RandomNormal",
	"Regular",
	"RegularJittered",
	"Grid",
};

const char * MATRIX_FORMAT_NAMES[] = {
	"Density",
	"Color_Density",
	"RGB_Density",
};

const char * GRADIENT_TYPE_NAMES[] = {
	"None",
	"Texture",
	"Apophysis",
	"GIMP",
};

const char * TRANSFORM_MODE_NAMES[] = {
	"Probability",
	"Fork",
};

// Returns number of bytes per pixel for given matrix format.
static uint MatrixFormatToPixelSize(MATRIX_FORMAT format)
{
	switch (format)
	{
	case MATRIX_FORMAT_DENSITY:       return sizeof(float);
	case MATRIX_FORMAT_COLOR_DENSITY: return sizeof(float) * 2;
	case MATRIX_FORMAT_RGB_DENSITY:   return sizeof(float) * 4;
	default: assert(0); return 0;
	}
}

static float LoadAngle(const common::tokdoc::Node &node)
{
	using namespace common::tokdoc;
	Node *subnode = node.FindFirstChild(EMPTY_STRING);
	if (subnode == NULL)
		throw Error("Invalid angle value.");
	float angle;
	NodeTo(angle, *subnode, true);
	subnode = subnode->FindNextSibling(EMPTY_STRING);
	if (subnode)
	{
		if (subnode->Value == "deg")
			angle = DegToRad(angle);
		else if (subnode->Value == "rad")
		{
		}
		else
			throw Error("Invalid angle unit.");
	}
	return angle;
}

/*
Parsing node representing 2D affine transform understands two notations:

1) Simple one
name = { a, b, c, d, e, f, g };

2) Complex one, made of individual transforms:
name = Affine2d {
	{ a, b, c, d, e, f, g };
	Translation { x, y };
	Scaling { s };
	Scaling { x, y };
	Rotation { x [deg|rad] };
	Identity;
};
*/
static void LoadAffine2d(AFFINE2D &out, const common::tokdoc::Node &node)
{
	using namespace common::tokdoc;

	if (node.Value.empty())
		NodeTo(out, node, true);
	else if (node.Value == "Affine2d")
	{
		out = AFFINE2D_IDENTITY;
		AFFINE2D tmp;
		for (Node *xformNode = node.FindFirstChild(EMPTY_STRING)
			; xformNode
			; xformNode = xformNode->FindNextSibling(EMPTY_STRING))
		{
			if (xformNode->Value.empty())
				NodeTo(tmp, *xformNode, true);
			else if (xformNode->Value == "Identity")
				continue;
			else if (xformNode->Value == "Translation")
			{
				VEC2 vec;
				NodeTo(vec, *xformNode, true);
				Translation(tmp, vec);
			}
			else if (xformNode->Value == "Scaling")
			{
				if (xformNode->CalcChildCount(EMPTY_STRING) == 1)
				{
					float s;
					SubnodeTo(s, *xformNode, EMPTY_STRING, SUBNODE_REQUIRED);
					Scaling(tmp, s);
				}
				else
				{
					VEC2 vec;
					NodeTo(vec, *xformNode, true);
					Scaling(tmp, vec);
				}
			}
			else if (xformNode->Value == "Rotation")
			{
				float angle = LoadAngle(*xformNode);
				Rotation(tmp, angle);
			}
			else
				throw Error("Invalid Affine2d transform.");

			out *= tmp;
		}
	}
	else
		throw Error("Invalid Affine2d definition.");
}


////////////////////////////////////////////////////////////////////////////////
// class ValueMatrix

class ValueMatrix
{
public:
	ValueMatrix(MATRIX_FORMAT format, uint cellCount);
	~ValueMatrix();

	void Clear();
	void *GetPtr() { return m_Ptr; }

private:
	MATRIX_FORMAT m_Format;
	uint m_CellCount;
	void *m_Ptr;
};

ValueMatrix::ValueMatrix( MATRIX_FORMAT format, uint cellCount )
: m_Format(format)
, m_CellCount(cellCount)
, m_Ptr(NULL)
{
	switch (format)
	{
	case MATRIX_FORMAT_DENSITY:
		m_Ptr = new float[cellCount];
		break;
	case MATRIX_FORMAT_COLOR_DENSITY:
		m_Ptr = new float[cellCount*2];
		break;
	case MATRIX_FORMAT_RGB_DENSITY:
		m_Ptr = new float[cellCount*4];
		break;
	default:
		assert(0);
	}
}

ValueMatrix::~ValueMatrix()
{
	delete [] (float*)m_Ptr;
}

void ValueMatrix::Clear()
{
	switch (m_Format)
	{
	case MATRIX_FORMAT_DENSITY:
		ZeroMemory(m_Ptr, m_CellCount*sizeof(float));
		break;
	case MATRIX_FORMAT_COLOR_DENSITY:
		ZeroMemory(m_Ptr, m_CellCount*sizeof(float)*2);
		break;
	case MATRIX_FORMAT_RGB_DENSITY:
		ZeroMemory(m_Ptr, m_CellCount*sizeof(float)*4);
		break;
	default:
		assert(0);
	}
}


////////////////////////////////////////////////////////////////////////////////
// struct InternalContext

struct InternalContext
{
	ValueMatrix *Matrix;
	// Acumulated - every element is sum of the probability of the transform and all preview transforms.
	std::vector<uint> TransformProbabilities;
	uint IterationIndex;
};

////////////////////////////////////////////////////////////////////////////////
// struct BaseStartDesc and inherited classes

BaseStartDesc::BaseStartDesc()
: ColorMin(1.f, 1.f, 1.f, 1.f)
, ColorMax(1.f, 1.f, 1.f, 1.f)
{
	for (uint i = 0; i < 4; i++)
		m_ColorConst[i] = false;
}

void BaseStartDesc::Load( const tokdoc::Node &node )
{
	using namespace common::tokdoc;
	SubnodeTo(ColorMin, node, "ColorMin", SUBNODE_OPTIONAL_CORRECT);
	SubnodeTo(ColorMax, node, "ColorMax", SUBNODE_OPTIONAL_CORRECT);
}

void BaseStartDesc::Preprocess()
{
	for (uint i = 0; i < 4; i++)
		m_ColorConst[i] = ColorMin[i] == ColorMax[i];
}

void BaseStartDesc::NewColor( COLORF &outColor ) const
{
	for (uint i = 0; i < 4; i++)
	{
		if (m_ColorConst[i])
			outColor[i] = ColorMin[i];
		else
			outColor[i] = g_Rand.RandFloat(ColorMin[i], ColorMax[i]);
	}
}

RandomStartDesc::RandomStartDesc()
: Count(0)
, Rect(RECTF_ZERO)
{
}

void RandomStartDesc::Load( const tokdoc::Node &node )
{
	__super::Load(node);
	using namespace common::tokdoc;
	SubnodeTo(Count, node, "Count", SUBNODE_REQUIRED);
	SubnodeTo(Rect, node, "Rect", SUBNODE_REQUIRED);
}


RandomNormalStartDesc::RandomNormalStartDesc()
: Count(0)
, Center(VEC2_ZERO)
, StdDev(VEC2_ZERO)
{

}

void RandomNormalStartDesc::Load( const tokdoc::Node &node )
{
	__super::Load(node);
	using namespace common::tokdoc;
	SubnodeTo(Count, node, "Count", SUBNODE_REQUIRED);
	SubnodeTo(Center, node, "Center", SUBNODE_REQUIRED);
	SubnodeTo(StdDev, node, "StdDev", SUBNODE_REQUIRED);
}

RegularStartDesc::RegularStartDesc()
: Count(POINT_ZERO)
, Rect(RECTF_ZERO)
, Jittered(false)
{
}

void RegularStartDesc::Load( const tokdoc::Node &node )
{
	__super::Load(node);
	using namespace common::tokdoc;
	SubnodeTo(Count, node, "Count", SUBNODE_REQUIRED);
	SubnodeTo(Rect, node, "Rect", SUBNODE_REQUIRED);
}

GridStartDesc::GridStartDesc()
: Rect(RECTF_ZERO)
, LineCount(POINT_ZERO)
, PointsPerLine(POINT_ZERO)
{
}

void GridStartDesc::Load( const tokdoc::Node &node )
{
	__super::Load(node);
	using namespace common::tokdoc;
	SubnodeTo(Rect, node, "Rect", SUBNODE_REQUIRED);
	SubnodeTo(LineCount, node, "LineCount", SUBNODE_REQUIRED);
	SubnodeTo(PointsPerLine, node, "PointsPerLine", SUBNODE_REQUIRED);
}

BaseStartDesc * CreateStartDesc(START_TYPE type)
{
	switch (type)
	{
	case START_RANDOM:
		return new RandomStartDesc();
	case START_RANDOM_NORMAL:
		return new RandomNormalStartDesc();
	case START_REGULAR:
	case START_REGULAR_JITTERED:
		{
			RegularStartDesc *rsd = new RegularStartDesc();
			rsd->Jittered = (type == START_REGULAR_JITTERED);
			return rsd;
		}
	case START_GRID:
		return new GridStartDesc();
	default:
		assert(0);
		return NULL;
	}
}

////////////////////////////////////////////////////////////////////////////////
// struct RenderDesc

void RenderDesc::Load( const tokdoc::Node &node, const POINT_ &textureSize )
{
	using namespace common::tokdoc;

	// PosScale and PosBias or PosRange
	Node *subnode = node.FindFirstChild("PosRange");
	if (subnode)
	{
		RECTF posRange;
		NodeTo(posRange, *subnode, true);
		CalcLinearFactors(PosScale.x, PosBias.x, posRange.Min.x, posRange.Max.x, 0.f, (float)textureSize.x);
		CalcLinearFactors(PosScale.y, PosBias.y, posRange.Min.y, posRange.Max.y, 0.f, (float)textureSize.y);
	}
	else
	{
		NodeTo(PosScale, node.MustFindFirstChild("PosScale"), true);
		NodeTo(PosBias, node.MustFindFirstChild("PosBias"), true);
	}

	NodeTo(Viewport, node.MustFindFirstChild("Viewport"), true);
	NodeTo(VisibleIterationRange, node.MustFindFirstChild("VisibleIterationRange"), true);
	SubnodeTo(Gamma, node, "Gamma", SUBNODE_OPTIONAL_CORRECT);
	SubnodeTo(LogColor, node, "LogColor", SUBNODE_OPTIONAL_CORRECT);
	SubnodeTo(ColorBias, node, "ColorBias", SUBNODE_OPTIONAL_CORRECT);
	SubnodeTo(ColorScale, node, "ColorScale", SUBNODE_OPTIONAL_CORRECT);
	TransparentBackground = ! SubnodeTo(BackgroundColor, node, "BackgroundColor", SUBNODE_OPTIONAL_CORRECT);

	subnode = node.FindFirstChild("Gradient");
	if (subnode)
	{
		if (subnode->Value == "None")
			GradientType = GRADIENT_NONE;
		else if (subnode->Value == "Texture")
			GradientType = GRADIENT_TEXTURE;
		else if (subnode->Value == "Apophysis")
			GradientType = GRADIENT_APOPHYSIS;
		else if (subnode->Value == "GIMP")
			GradientType = GRADIENT_GIMP;
		else
			throw Error("Error in RenderParams.Gradient.");

		if (GradientType == GRADIENT_TEXTURE || GradientType == GRADIENT_APOPHYSIS || GradientType == GRADIENT_GIMP)
			SubnodeTo(GradientFilePath, *subnode, "File", SUBNODE_REQUIRED);
		if (GradientType == GRADIENT_APOPHYSIS)
			SubnodeTo(GradientName, *subnode, "Name", SUBNODE_REQUIRED);
	}
}

void RenderDesc::Preprocess()
{
	if (Gamma <= 0.f)
		throw Error(Format("Invalid Gamma: #") % Gamma);

	GammaCorrection = Gamma != 1.f;
	ColorBiasZero = ColorBias == COLORF(0.f, 0.f, 0.f, 0.f);

	m_Viewport_f.Min.x = (float)Viewport.Min.x;
	m_Viewport_f.Min.y = (float)Viewport.Min.y;
	m_Viewport_f.Max.x = (float)Viewport.Max.x;
	m_Viewport_f.Max.y = (float)Viewport.Max.y;
}

RenderDesc::RenderDesc()
: Gamma(1.f)
, GammaCorrection(false)
, LogColor(false)
, ColorScale(1.f, 1.f, 1.f, 1.f)
, ColorBias(0.f, 0.f, 0.f, 0.f)
, ColorBiasZero(false)
, TransparentBackground(true)
, BackgroundColor(COLORF_BLACK)
, GradientType(GRADIENT_NONE)
, m_Viewport_f(RECTF_ZERO)
{

}

Desc::Desc()
: MatrixFormat(MATRIX_FORMAT_COUNT)
{

}

void Desc::LoadStartParams(const tokdoc::Node &node)
{
	using namespace common::tokdoc;
	START_TYPE startType;
	NodeValueToEnum((uint&)startType, node, true, START_TYPE_NAMES, NULL, START_COUNT);
	StartParams.reset(CreateStartDesc(startType));
	StartParams->Load(node);
}

void Desc::Load( const tokdoc::Node &node )
{
	using namespace common::tokdoc;
	NodeTo(TextureSize, node.MustFindFirstChild("TextureSize"), true);
	NodeTo(IterationCount, node.MustFindFirstChild("IterationCount"), true);
	SubnodeTo(ColorWeight, node, "ColorWeight", SUBNODE_REQUIRED);
	NodeValueToEnum((uint&)MatrixFormat, node.MustFindFirstChild("MatrixFormat"), true, MATRIX_FORMAT_NAMES, NULL, MATRIX_FORMAT_COUNT);

	Node *n = node.FindFirstChild("TransformMode");
	if (n)
		NodeValueToEnum((uint&)TransformMode, *n, true, TRANSFORM_MODE_NAMES, NULL, TRANSFORM_MODE_COUNT);

	LoadStartParams(node.MustFindFirstChild("StartParams"));

	RenderParams.Load(node.MustFindFirstChild("RenderParams"), TextureSize);

	Node transformsNode = node.MustFindFirstChild("Transforms");
	TransformMode = TRANSFORM_MODE_FORK;

	for (Node *transformNode = transformsNode.GetFirstChild(); transformNode; transformNode = transformNode->GetNextSibling())
	{
		TransformFunction *transform = LoadTransform(*transformNode);
		Transforms.push_back( shared_ptr<TransformFunction>( transform ) );
		if (transform->Probability > 0)
			TransformMode = TRANSFORM_MODE_PROBABILITY;
	}

	n = node.FindFirstChild("FinalTransform");
	if (n)
		FinalTransform.reset(LoadTransform(*n));
	else
		FinalTransform.reset();
}

TransformFunction * Desc::LoadTransform(const tokdoc::Node &transformNode)
{
	TransformFunction *xform = new TransformFunction();
	xform->Load(transformNode);
	return xform;
}

void Desc::Preprocess()
{
	assert(StartParams.get());
	StartParams->Preprocess();
	RenderParams.Preprocess();
	if (!FinalTransform.is_null())
		FinalTransform->Preprocess();
	for (uint i = 0; i < Transforms.size(); i++)
		Transforms[i]->Preprocess();

	if (!RenderParams.Viewport.IsValid())
		throw Error("Invalid viewport.");
	
	if (Transforms.size() == 0)
		throw Error("No transforms specified.");
	
	if (RenderParams.VisibleIterationRange.x < 0
		|| RenderParams.VisibleIterationRange.y < 0
		|| RenderParams.VisibleIterationRange.y < RenderParams.VisibleIterationRange.x)
		throw Error("Invalid RenderParams.VisibleIterationRange.");

	if (TransformMode == TRANSFORM_MODE_PROBABILITY)
	{
		uint transformProbabilitySum = 0;
		for (uint i = 0; i < Transforms.size(); i++)
			transformProbabilitySum += Transforms[i]->Probability;
		if (transformProbabilitySum == 0)
			throw Error("Sum of transform probabilities must be positive number.");
	}

	if (RenderParams.Viewport.Min.x < 0)
		RenderParams.Viewport.Min.x = 0;
	if (RenderParams.Viewport.Min.y < 0)
		RenderParams.Viewport.Min.y = 0;
	if (RenderParams.Viewport.Max.x > TextureSize.x)
		RenderParams.Viewport.Max.x = TextureSize.x;
	if (RenderParams.Viewport.Max.y > TextureSize.y)
		RenderParams.Viewport.Max.y = TextureSize.y;

	bool colorWeightWarning = false;
	for (uint i = 0; i < 4; i++)
	{
		if (ColorWeight[i] <= 0.f || ColorWeight[i] > 1.f)
		{
			colorWeightWarning = true;
			break;
		}
	}
	if (colorWeightWarning)
		LOG(LOG_APP | LOG_WARNING, Format("ColorWeight=#") % ColorWeight);
}

////////////////////////////////////////////////////////////////////////////////
// struct TransformFunction

const char * TransformFunction::TYPE_NAMES[] = {
	"Linear",
	"Sinusoidal",
	"Spherical",
	"Swirl",
	"Horseshoe",
	"Polar",
	"Handkerchief",
	"Heart",
	"Disc",
	"Spiral",
	"Hyperbolic",
	"Diamond",
	"Ex",
	"Julia",
	"Bent",
	"Waves",
	"Fisheye",
	"Popcorn",
	"Exponential",
	"Power",
	"Cosine",
	"Rings",
	"Fan",
	"Blob",
	"Pdj",
	"Fan2",
	"Rings2",
	"Eyefish",
	"Bubble",
	"Cylinder",
	"Perspective",
	"Noise",
	"JuliaN",
	"JuliaScope",
	"Blur",
	"Gaussian",
	"Pie",
	"Ngon",
	"Curl",
	"Rectangles",
	"Tangent",
	"Square",
	"Cross",
};

TransformFunction::TransformFunction()
: Probability(0)
, ProcessColor(false)
, Color(1.f, 1.f, 1.f, 1.f)
, Type(TYPE_LINEAR)
, PreTransform(AFFINE2D_IDENTITY)
, PostTransform(AFFINE2D_IDENTITY)
, PreTransformIdentity(false)
, PostTransformIdentity(false)
{
	ZeroMemory(Params, sizeof(Params));
}

void TransformFunction::Load( const tokdoc::Node &node )
{
	using namespace common::tokdoc;
	
	SubnodeTo(Probability, node, "Probability", SUBNODE_OPTIONAL_CORRECT);
	
	Node *subnode = node.FindFirstChild("Color");
	if (subnode)
	{
		ProcessColor = true;
		NodeTo(Color, *subnode, true);
	}
	else
		ProcessColor = false;

	Node &typeNode = node.MustFindFirstChild("Type");
	NodeValueToEnum((uint&)Type, typeNode, true, TYPE_NAMES, NULL, TYPE_COUNT);

	subnode = node.FindFirstChild("PreTransform");
	if (subnode)
		LoadAffine2d(PreTransform, *subnode);
	subnode = node.FindFirstChild("PostTransform");
	if (subnode)
		LoadAffine2d(PostTransform, *subnode);

	Node *paramsNode = node.FindFirstChild("Params");
	if (paramsNode)
	{
		unsigned i = 0;
		for (Node *paramNode = paramsNode->FindFirstChild(EMPTY_STRING)
			; paramNode != NULL && i < _countof(Params);
			paramNode = paramNode->FindNextSibling(EMPTY_STRING), i++)
		{
			NodeTo(Params[i], *paramNode, true);
		}
	}
}

void TransformFunction::TransformPoint( VEC2 &p, InternalContext &ctx )
{
	VEC2 tmp;

	if (!PreTransformIdentity)
	{
		Transform(tmp, p, PreTransform);
		p = tmp;
	}

	switch (Type)
	{
	case TYPE_LINEAR:
		break;
	case TYPE_SINUSOIDAL:
		p = VEC2(sinf(p.x), sinf(p.y));
		break;
	case TYPE_SPHERICAL:
		p /= LengthSq(p);
		break;
	case TYPE_SWIRL:
		{
			float r2 = LengthSq(p);
			float sinr2, cosr2;
			sincos(r2, &sinr2, &cosr2);
			p = VEC2(
				p.x*sinr2 - p.y*cosr2,
				p.x*cosr2 + p.y*sinr2);
		}
		break;
	case TYPE_HORSESHOE:
		p = VEC2( (p.x+p.y) * (p.x-p.y), 2.f*p.x*p.y ) / Length(p);
		break;
	case TYPE_POLAR:
		{
			float teta = atan2f(p.x, p.y);
			float r = Length(p);
			p = VEC2(teta * _1_PI, r - 1.f);
		}
		break;
	case TYPE_HANDKERCHIEF:
		{
			float teta = atan2f(p.x, p.y);
			float r = Length(p);
			p = VEC2( r*sinf(teta+r), r*cosf(teta-r) );
		}
		break;
	case TYPE_HEART:
		{
			float r = Length(p);
			float teta_mul_r = atan2f(p.x, p.y) * r;
			p = VEC2( r*sinf(teta_mul_r), -r*cosf(teta_mul_r) );
		}
		break;
	case TYPE_DISC:
		{
			//teta/pi * ( sin(pi*r), cos(pi*r) )
			float pi_r = PI * Length(p);
			float sinVal, cosVal;
			sincos(pi_r, &sinVal, &cosVal);
			float factor = atan2f(p.x, p.y) * _1_PI;
			p = VEC2( factor*sinVal, factor*cosVal );
		}
		break;
	case TYPE_SPIRAL:
		{
			float r = Length(p);
			float teta = atan2f(p.x, p.y);
			float factor = teta * _1_PI;
			float sinTeta, cosTeta, sinR, cosR;
			sincos(teta, &sinTeta, &cosTeta);
			sincos(r, &sinR, &cosR);
			p = VEC2( factor*(cosTeta+sinR), factor*(sinTeta-cosR) );
		}
		break;
	case TYPE_HYPERBOLIC:
		{
			float r = Length(p);
			float teta = atan2f(p.x, p.y);
			float sinTeta, cosTeta;
			sincos(teta, &sinTeta, &cosTeta);
			p = VEC2( sinTeta/r, cosTeta*r );
		}
		break;
	case TYPE_DIAMOND:
		// sin(teta)*cos(r), cos(teta)*sin(r)
		{
			float r = Length(p);
			float teta = atan2f(p.x, p.y);
			float sinTeta, cosTeta, sinR, cosR;
			sincos(teta, &sinTeta, &cosTeta);
			sincos(r, &sinR, &cosR);
			p = VEC2( sinTeta*cosR, cosTeta*sinR );
		}
		break;
	case TYPE_EX:
		{
			float r = Length(p);
			float teta = atan2f(p.x, p.y);
			float p0 = sinf(teta+r), p1 = cosf(teta-r);
			float p0_3 = p0*p0*p0, p1_3 = p1*p1*p1;
			p = VEC2( r*(p0_3+p1_3), r*(p0_3-p1_3) );
		}
		break;
	case TYPE_JULIA:
		// sqrt(r) * ( cos(teta/2)+OMEGA, sin(teta/2+OMEGA) )
		{
			float r = Length(p);
			float teta = atan2f(p.x, p.y);
			float OMEGA = g_Rand.RandBool() ? 0.f : PI;
			float sinVal, cosVal;
			sincos(teta*0.5f+OMEGA, &sinVal, &cosVal);
			float r_sqrt = sqrtf(r);
			p = VEC2( r_sqrt*cosVal, r_sqrt*sinVal );
		}
		break;
	case TYPE_BENT:
		{
			if (p.x < 0.f)
				p.x *= 2.f;
			if (p.y < 0.f)
				p.y *= 0.5f;
		}
		break;
	case TYPE_WAVES:
		{
			p = VEC2(
				p.x + PreTransform.b*sinf(p.y/(PreTransform.c*PreTransform.c)),
				p.y + PreTransform.e*sinf(p.x/(PreTransform.f*PreTransform.f)) );
		}
		break;
	case TYPE_FISHEYE:
		// 2/(r+1) * (y, x)
		{
			float r = Length(p);
			float factor = 2.f / (r + 1.f);
			p = VEC2( factor*p.y, factor*p.x );
		}
		break;
	case TYPE_POPCORN:
		p = VEC2(
			p.x + PreTransform.c * sinf(tanf(3.f*p.y)),
			p.y + PreTransform.f * sinf(tanf(3.f*p.x)) );
		break;
	case TYPE_EXPONENTIAL:
		{
			float pi_y = PI * p.y;
			float sinVal, cosVal;
			sincos(pi_y, &sinVal, &cosVal);
			float factor = expf(p.x-1.f);
			p = VEC2( factor*cosVal, factor*sinVal );
		}
		break;
	case TYPE_POWER:
		{
			float r = Length(p);
			float teta = atan2f(p.x, p.y);
			float sinTeta, cosTeta;
			sincos(teta, &sinTeta, &cosTeta);
			float factor = powf(r, sinTeta);
			p = VEC2( factor*cosTeta, factor*sinTeta );
		}
		break;
	case TYPE_COSINE:
		{
			float pi_x = PI * p.x;
			float sinPiX, cosPiX;
			sincos(pi_x, &sinPiX, &cosPiX);
			p = VEC2( cosPiX*coshf(p.y), -sinPiX*sinhf(p.y) );
		}
		break;
	case TYPE_RINGS:
		{
			float r = Length(p);
			float teta = atan2f(p.x, p.y);
			float sinTeta, cosTeta;
			sincos(teta, &sinTeta, &cosTeta);
			float c2 = PreTransform.c*PreTransform.c;
			float factor = fmodf(r+c2, 2.f*c2) - c2 + r*(1.f-c2);
			p = VEC2( factor*cosTeta, factor*sinTeta );
		}
		break;
	case TYPE_FAN:
		{
			float r = Length(p);
			float teta = atan2f(p.x, p.y);
			float t = PI * PreTransform.c*PreTransform.c;
			float tHalf = t * 0.5f;
			float ifVal = fmodf(teta+PreTransform.f, t);
			float valForSincos;
			if (ifVal > tHalf)
				valForSincos = teta - tHalf;
			else
				valForSincos = teta + tHalf;
			float sinVal, cosVal;
			sincos(valForSincos, &sinVal, &cosVal);
			p = VEC2( r*cosVal, r*sinVal );
		}
		break;
	case TYPE_BLOB:
		// r*(Params[1] + (Params[0]-Params[1])/2 * (sin(Params[2]*teta) + 1)) * ( cos(teta), sin(teta) )
		{
			float r = Length(p);
			float teta = atan2f(p.x, p.y);

			float sinTeta, cosTeta;
			sincos(teta, &sinTeta, &cosTeta);

			float factor = r * ( Params[1] + (Params[0]-Params[1])*0.5f * (sinf(Params[2]*teta)+1.f) );
			p = VEC2( factor*cosTeta, factor*sinTeta );
		}
		break;
	case TYPE_PDJ:
		p = VEC2(
			sinf(Params[0]*p.y) - cosf(Params[1]*p.x),
			sinf(Params[2]*p.x) - cosf(Params[3]*p.y) );
		break;
	case TYPE_FAN2:
		{
			float r = Length(p);
			float teta = atan2f(p.x, p.y);
			float p1_half = Params[0] * 0.5f;

			float t = teta + Params[1] - Params[0]*trunc( 2.f*teta*Params[1] / Params[0] );

			float sinCosVal;
			if (t > p1_half)
				sinCosVal = teta - p1_half;
			else
				sinCosVal = teta + p1_half;
			float sinVal, cosVal;
			sincos(sinCosVal, &sinVal, &cosVal);

			p = VEC2( r*sinVal, r*cosVal );
		}
		break;
	case TYPE_RINGS2:
		{
			float r = Length(p);
			float teta = atan2f(p.x, p.y);

			float t = r - 2.f*Params[0]*trunc((r+Params[0])/(2.f*Params[0])) + r*(1.f-Params[0]);

			float sinTeta, cosTeta;
			sincos(teta, &sinTeta, &cosTeta);
			p = VEC2( t*sinTeta, t*cosTeta );
		}
		break;
	case TYPE_EYEFISH:
		{
			float r = Length(p);
			float factor = 2.f / (r+1.f);
			p = VEC2( factor*p.x, factor*p.y );
		}
		break;
	case TYPE_BUBBLE:
		{
			float r = Length(p);
			float factor = 4.f / (r*r+4.f);
			p = VEC2( factor*p.x, factor*p.y );
		}
		break;
	case TYPE_CYLINDER:
		p.x = sinf(p.x);
		break;
	case TYPE_PERSPECTIVE:
		{
			float sinP1, cosP1;
			sincos(Params[0], &sinP1, &cosP1);
			float factor = Params[1] / ( Params[1] - p.y*sinP1 );
			p = VEC2( factor*p.x, factor*p.y*cosP1 );
		}
		break;
	case TYPE_NOISE:
		{
			float psi1 = g_Rand.RandFloat(), psi2 = g_Rand.RandFloat();
			float factor = psi1;
			float two_pi_psi2 = PI_X_2 * psi2;
			float sinVal, cosVal;
			sincos(two_pi_psi2, &sinVal, &cosVal);
			p = VEC2( factor*p.x*cosVal, factor*p.y*sinVal );
		}
		break;
	case TYPE_JULIA_N:
		{
			float r = Length(p);
			float fi = atan2f(p.y, p.x);
			float psi = g_Rand.RandFloat();
			float p3 = trunc(fabsf(Params[0])*psi);
			float t = (fi + PI_X_2*p3) / Params[0];
			float factor = powf(r, Params[1]/Params[0]);
			float sinT, cosT;
			sincos(t, &sinT, &cosT);
			p = VEC2( factor*cosT, factor*sinT );
		}
		break;
	case TYPE_JULIA_SCOPE:
		{
			float r = Length(p);
			float fi = atan2f(p.y, p.x);
			float psi = g_Rand.RandFloat();
			float ALFA = g_Rand.RandBool() ? -1.f : 1.f;
			float p3 = trunc(fabsf(Params[0])*psi);
			float t = (ALFA*fi + PI_X_2*p3) / Params[0];
			float factor = powf(r, Params[1]/Params[0]);
			float sinT, cosT;
			sincos(t, &sinT, &cosT);
			p = VEC2( factor*cosT, factor*sinT );
		}
		break;
	case TYPE_BLUR:
		{
			float psi1 = g_Rand.RandFloat(), psi2 = g_Rand.RandFloat();
			float two_pi_psi2 = PI_X_2*psi2;
			float sinVal, cosVal;
			sincos(two_pi_psi2, &sinVal, &cosVal);
			float factor = psi1;
			p = VEC2( factor*cosVal, factor*sinVal );
		}
		break;
	case TYPE_GAUSSIAN:
		{
			float two_pi_psi5 = PI_X_2*g_Rand.RandFloat();
			float sinVal, cosVal;
			sincos(two_pi_psi5, &sinVal, &cosVal);
			float factor = g_Rand.RandFloat()
				+ g_Rand.RandFloat()
				+ g_Rand.RandFloat()
				+ g_Rand.RandFloat()
				- 2.f;
			p = VEC2( factor*cosVal, factor*sinVal );
		}
		break;
	case TYPE_PIE:
		{
			float t1 = trunc( g_Rand.RandFloat()*Params[0] + 0.5f );
			float t2 = Params[1] + (PI_X_2/Params[0])*(t1 + g_Rand.RandFloat()*Params[2]);
			float sin_t2, cos_t2;
			sincos(t2, &sin_t2, &cos_t2);
			float factor = g_Rand.RandFloat();
			p = VEC2( factor*cos_t2, factor*sin_t2 );
		}
		break;
	case TYPE_NGON:
		{
			float r = Length(p);
			float fi = atan2f(p.y, p.x);
			float t3 = fi - Params[1]*floorf(fi/Params[1]);
			float t4;
			if (t3 > Params[1]*0.5f)
				t4 = t3;
			else
				t4 = t3 - Params[1];
			float k = ( Params[2]*(1.f/(cosf(t4)) - 1.f) + Params[3] ) / powf(r, Params[0]);
			p *= k;
		}
		break;
	case TYPE_CURL:
		{
			float t1 = 1.f + Params[0]*p.x + Params[1]*(p.x*p.x - p.y*p.y);
			float t2 = Params[0]*p.y + 2.f*Params[1]*p.x*p.y;
			float factor = 1.f / (t1*t1 + t2*t2);
			p = VEC2( factor*(p.x*t1+p.y*t2), factor*(p.y*t1-p.x*t2) );
		}
		break;
	case TYPE_RECTANGLES:
		{
			p = VEC2(
				(2.f*floorf(p.x/Params[0])+1.f)*Params[0] - p.x,
				(2.f*floorf(p.y/Params[1])+1.f)*Params[1] - p.y );
		}
		break;
	case TYPE_TANGENT:
		{
			p = VEC2(
				sinf(p.x) / cosf(p.y),
				tanf(p.y) );
		}
		break;
	case TYPE_SQUARE:
		p.x = g_Rand.RandFloat(-0.5f, 0.5f);
		p.y = g_Rand.RandFloat(-0.5f, 0.5f);
		break;
	case TYPE_CROSS:
		p *= sqrtf( 1.f / sqr(p.x*p.x - p.y*p.y) );
		break;
	default:
		assert(0);
	}

	if (!PostTransformIdentity)
	{
		Transform(tmp, p, PostTransform);
		p = tmp;
	}
}

void TransformFunction::Preprocess()
{
	PreTransformIdentity = PreTransform == AFFINE2D_IDENTITY;
	PostTransformIdentity = PostTransform == AFFINE2D_IDENTITY;
}

static void CalcTransformProbabilities(std::vector<uint> &outProbabilities, const Desc &desc)
{
	uint count = desc.Transforms.size();
	outProbabilities.resize(count);
	if (count)
	{
		outProbabilities[0] = desc.Transforms[0]->Probability;
		uint count = desc.Transforms.size();
		for (uint i = 1; i < count; i++)
			outProbabilities[i] = outProbabilities[i-1] + desc.Transforms[i]->Probability;
	}
}

////////////////////////////////////////////////////////////////////////////////
// class Screen

Screen::Screen()
: m_TextureSize(POINT_ZERO)
{
}

Screen::~Screen()
{
	g_FileChangeMonitor->UnregisterAllFilesByListener(this);
}

void Screen::Init()
{
	RelativeToAbsolutePath(&m_FilePath, frame::DataDir, "IfsFractal01");
	g_FileChangeMonitor->RegisterFile(this, m_FilePath);

	NewFractal();
}

bool Screen::OnMouseMove( const common::VEC2 &Pos )
{
	if (m_Texture.get())
		return m_Camera.OnMouseMove(Pos);
	else
		return false;
}

bool Screen::OnMouseButton( const common::VEC2 &Pos, frame::MOUSE_BUTTON Button, frame::MOUSE_ACTION Action )
{
	if (m_Texture.get())
		return m_Camera.OnMouseButton(Pos, Button, Action);
	else
		return false;
}

bool Screen::OnMouseWheel( const common::VEC2 &Pos, float Delta )
{
	if (m_Texture.get())
		return m_Camera.OnMouseWheel(Pos, Delta);
	else
		return false;
}

void Screen::Update()
{
	m_Camera.Update();
}

void Screen::Render3d()
{
	D3DPERF_EVENT(L"IFS Fractal");

	frame::Dev->Clear(0, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, 0xff000000, 1.f, 0);

	if (m_Texture.get() == NULL)
		return;

	VEC2 textureSize = VEC2((float)m_TextureSize.x, (float)m_TextureSize.y);
	RECTF rect;
	m_Camera.UnitsToScreen(rect.Min, VEC2_ZERO);
	m_Camera.UnitsToScreen(rect.Max, textureSize);
	rect += VEC2(-0.5f, -0.5f); // Correction for pixel perfection

	RenderTexturePreview(rect, m_Texture.get(),
		D3DCOLORWRITEENABLE_RED | D3DCOLORWRITEENABLE_GREEN | D3DCOLORWRITEENABLE_BLUE | D3DCOLORWRITEENABLE_ALPHA);
}

void Screen::ResetView()
{
	if (m_Texture.get())
	{
		m_Camera.Scale = 1.f;
		m_Camera.Bias.x = frame::GetScreenWidth() *0.5f - (float)m_TextureSize.x*0.5f;
		m_Camera.Bias.y = frame::GetScreenHeight()*0.5f - (float)m_TextureSize.y*0.5f;
	}
	else
	{
		m_Camera.Scale = 1.f;
		m_Camera.Bias = VEC2_ZERO;
	}
}

void Screen::NewFractal()
{
	m_ErrorMessage.clear();
	m_Texture.reset();
	m_TextureSize = POINT_ZERO;

	try
	{
		Desc desc;
		{
			common::tokdoc::Node doc;
			{
				LOG(LOG_APP, "Loading fractal description from \"" + m_FilePath + "\".");
				FileStream fileStream(m_FilePath, FM_READ);
				Tokenizer tokenizer(&fileStream, Tokenizer::FLAG_MULTILINE_STRINGS);
				tokenizer.Next();
				doc.LoadChildren(tokenizer);
			}
			desc.Load(doc);
		}
		MakeFractal(desc);
	}
	catch (const Error &e)
	{
		e.GetMessage_(&m_ErrorMessage);
		m_Texture.reset();
	}
}

void Screen::Render2d()
{
	if (!m_ErrorMessage.empty())
	{
		using namespace gfx2d;

		assert(GetMainFont());
		
		g_Canvas->SetFont(GetMainFont());
		g_Canvas->SetColor(COLOR_RED);
		g_Canvas->DrawText_(
			frame::GetBackBufferSize()*0.5f,
			m_ErrorMessage,
			0.f,
			Font::FLAG_HCENTER | Font::FLAG_VMIDDLE | Font::FLAG_WRAP_WORD | Font::FLAG_PIXEL_PERFECT,
			frame::GetBackBufferSize().x);
	}
}

void Screen::GenerateValueArray( ValueMatrix &matrix, const Desc &desc )
{
	matrix.Clear();

	InternalContext ctx;
	ctx.Matrix = &matrix;
	ctx.IterationIndex = 0xFFFFFFFF;

	if (desc.TransformMode == TRANSFORM_MODE_PROBABILITY)
		CalcTransformProbabilities(ctx.TransformProbabilities, desc);

	COLORF startColor;
	VEC2 pos;

	const BaseStartDesc *baseStartDesc = desc.StartParams.get();
	if (typeid(*baseStartDesc) == typeid(RandomStartDesc))
	{
		const RandomStartDesc &startDesc = (const RandomStartDesc&)*baseStartDesc;
		for (uint i = 0; i < startDesc.Count; i++)
		{
			startDesc.NewColor(startColor);
			ProcessPoint(ctx, desc, VEC2(
				g_Rand.RandFloat(startDesc.Rect.Min.x, startDesc.Rect.Max.x),
				g_Rand.RandFloat(startDesc.Rect.Min.y, startDesc.Rect.Max.y)),
				startColor);
		}
	}
	else if (typeid(*baseStartDesc) == typeid(RandomNormalStartDesc))
	{
		const RandomNormalStartDesc &startDesc = (const RandomNormalStartDesc&)*baseStartDesc;
		for (uint i = 0; i < startDesc.Count; i++)
		{
			startDesc.NewColor(startColor);
			pos.x = g_Rand.RandNormal(startDesc.StdDev.x) + startDesc.Center.x;
			pos.y = g_Rand.RandNormal(startDesc.StdDev.y) + startDesc.Center.y;
			ProcessPoint(ctx, desc, pos, startColor);
		}
	}
	else if (typeid(*baseStartDesc) == typeid(RegularStartDesc))
	{
		const RegularStartDesc &startDesc = (const RegularStartDesc&)*baseStartDesc;
		VEC2 pos, posStep = VEC2(
			startDesc.Rect.GetSizeDim(0) / (float)startDesc.Count.x,
			startDesc.Rect.GetSizeDim(1) / (float)startDesc.Count.y);
		VEC2 startPos = startDesc.Rect.Min + posStep*0.5f;

		if (!startDesc.Jittered)
		{
			pos.y = startPos.y;
			for (int y = 0; y < startDesc.Count.y; y++)
			{
				pos.x = startPos.x;
				for (int x = 0; x < startDesc.Count.x; x++)
				{
					startDesc.NewColor(startColor);
					ProcessPoint(ctx, desc, pos, startColor);
					pos.x += posStep.x;
				}
				pos.y += posStep.y;
			}
		}
		else
		{
			VEC2 jitter, jitterHalfSize = posStep*0.5f;
			RECTF jitterRect = RECTF(-jitterHalfSize, jitterHalfSize);

			pos.y = startPos.y;
			for (int y = 0; y < startDesc.Count.y; y++)
			{
				pos.x = startPos.x;
				for (int x = 0; x < startDesc.Count.x; x++)
				{
					jitter = VEC2(
						g_Rand.RandFloat(jitterRect.Min.x, jitterRect.Max.x),
						g_Rand.RandFloat(jitterRect.Min.y, jitterRect.Max.y) );
					startDesc.NewColor(startColor);
					ProcessPoint(ctx, desc, pos + jitter, startColor);
					pos.x += posStep.x;
				}
				pos.y += posStep.y;
			}
		}
	}
	else if (typeid(*baseStartDesc) == typeid(GridStartDesc))
	{
		const GridStartDesc &startDesc = (const GridStartDesc&)*baseStartDesc;
		VEC2 step;
		// Vertical lines
		step.x = startDesc.Rect.GetSizeDim(0) / (float)startDesc.LineCount.x;
		step.y = startDesc.Rect.GetSizeDim(1) / (float)startDesc.PointsPerLine.x;
		pos.x = startDesc.Rect.Min.x;
		for (int line_i = 0; line_i <= startDesc.LineCount.x; line_i++, pos.x += step.x)
		{
			pos.y = startDesc.Rect.Min.y;
			for (int point_i = 0; point_i < startDesc.PointsPerLine.x; point_i++, pos.y += step.y)
			{
				startDesc.NewColor(startColor);
				ProcessPoint(ctx, desc, pos, startColor);
			}
		}
		// Horizontal lines
		step.x = startDesc.Rect.GetSizeDim(0) / (float)startDesc.PointsPerLine.y;
		step.y = startDesc.Rect.GetSizeDim(1) / (float)startDesc.LineCount.y;
		pos.y = startDesc.Rect.Min.y;
		for (int line_i = 0; line_i <= startDesc.LineCount.x; line_i++, pos.y += step.y)
		{
			pos.x = startDesc.Rect.Min.x;
			for (int point_i = 0; point_i < startDesc.PointsPerLine.x; point_i++, pos.x += step.x)
			{
				startDesc.NewColor(startColor);
				ProcessPoint(ctx, desc, pos, startColor);
			}
		}
	}
	else
		assert(0);
}

void Screen::ProcessPoint(InternalContext &ctx, const Desc &desc, const VEC2 &pos, const COLORF &color)
{
	switch (desc.TransformMode)
	{
	case TRANSFORM_MODE_PROBABILITY:
		ProcessPoint_Probability(ctx, desc, pos, color);
		break;
	case TRANSFORM_MODE_FORK:
		ProcessPoint_Fork(ctx, desc, pos, 0, 0.f, color);
		break;
	default:
		assert(0);
	}
}

void Screen::ProcessPoint_Probability( InternalContext &ctx, const Desc &desc, const VEC2 &pos, const COLORF &color )
{
	VEC2 currentPos = pos;
	COLORF currentColor = color;
	ctx.IterationIndex = 0;

	DrawPoint(ctx, desc, currentPos, currentColor);

	for (ctx.IterationIndex = 1; ctx.IterationIndex <= desc.IterationCount; ctx.IterationIndex++)
	{
		uint transformIndex = 0, transformCount = desc.Transforms.size();
		if (transformCount > 1)
		{
			uint randNum = g_Rand.RandUint(ctx.TransformProbabilities[transformCount-1]);
			while (randNum >= ctx.TransformProbabilities[transformIndex])
				transformIndex++;
		}
		TransformFunction &transform = *desc.Transforms[transformIndex].get();

		transform.TransformPoint(currentPos, ctx);
		if (transform.ProcessColor)
			TransformColor(currentColor, transform, desc);

		DrawPoint(ctx, desc, currentPos, currentColor);
	}
}

void Screen::TransformColor(COLORF &inoutColor, const TransformFunction &transform, const Desc &desc)
{
	inoutColor.R = Lerp(inoutColor.R, transform.Color.R, desc.ColorWeight.R);
	inoutColor.G = Lerp(inoutColor.G, transform.Color.G, desc.ColorWeight.G);
	inoutColor.B = Lerp(inoutColor.B, transform.Color.B, desc.ColorWeight.B);
}

void Screen::DrawPoint(InternalContext &ctx, const Desc &desc, const VEC2 &pos, const COLORF &color)
{
	if (ctx.IterationIndex >= (uint)desc.RenderParams.VisibleIterationRange.x
		&& ctx.IterationIndex < (uint)desc.RenderParams.VisibleIterationRange.y)
	{
		VEC2 finalPos = pos;
		if (desc.FinalTransform.get())
			desc.FinalTransform->TransformPoint(finalPos, ctx);

		VEC2 viewPosf;
		Mul(&viewPosf, finalPos, desc.RenderParams.PosScale);
		viewPosf += desc.RenderParams.PosBias + VEC2(0.5f, 0.5f);

		// Comparing point position with floating-point viewport rectangle defeats a strange bug with overbright first row. I don't understand this.
		const RECTF & viewport_f = desc.RenderParams.GetViewport_f();
		if (viewPosf.x > viewport_f.Min.x
			&& viewPosf.y > viewport_f.Min.y
			&& viewPosf.x < viewport_f.Max.x
			&& viewPosf.y < viewport_f.Max.y)
		{	
			POINT_ viewPosi = POINT_((int)viewPosf.x, (int)viewPosf.y);
		/*if (viewPosi.x >= desc.RenderParams.Viewport.Min.x
			&& viewPosi.y >= desc.RenderParams.Viewport.Min.y
			&& viewPosi.x < desc.RenderParams.Viewport.Max.x
			&& viewPosi.y < desc.RenderParams.Viewport.Max.y)*/
			float *matrixPtr = (float*)ctx.Matrix->GetPtr();
			switch (desc.MatrixFormat)
			{
			case MATRIX_FORMAT_DENSITY:
				{
					float *pixelPtr = matrixPtr + (viewPosi.y * desc.TextureSize.x + viewPosi.x);
					(*pixelPtr) += 1.f;
				}
				break;
			case MATRIX_FORMAT_COLOR_DENSITY:
				{
					float *pixelPtr = matrixPtr + (viewPosi.y * desc.TextureSize.x + viewPosi.x) * 2;
					pixelPtr[0] += color.R;
					pixelPtr[1] += 1.f;
				}
				break;
			case MATRIX_FORMAT_RGB_DENSITY:
				{
					float *pixelPtr = matrixPtr + (viewPosi.y * desc.TextureSize.x + viewPosi.x) * 4;
					pixelPtr[0] += color.R;
					pixelPtr[1] += color.G;
					pixelPtr[2] += color.B;
					pixelPtr[3] += 1.f;
				}
				break;
			};
		}
	}
}

void Screen::RenderValuesToTexture( IDirect3DTexture9 *texture, ValueMatrix &matrix, const Desc &desc, gradients::BaseGradient *gradient )
{
	bool gammaCorrection = desc.RenderParams.IsGammaCorrection();
	float gammaExponent = 1.f / desc.RenderParams.Gamma;
	bool useColorBias = !desc.RenderParams.IsColorBiasZero();
	COLOR colorBiasAsColor = ColorfToColor(desc.RenderParams.ColorBias);
	
	float toneMappingFactor = CalcToneMappingFactor(desc, matrix);
	LOG(1, Format("ToneMappingFactor=#") % toneMappingFactor);

	uint matrixCellSize = MatrixFormatToPixelSize(desc.MatrixFormat);
	const BYTE *matrixCellPtr = (const BYTE*)matrix.GetPtr();

	D3DLOCKED_RECT lockedRect;
	texture->LockRect(0, &lockedRect, NULL, 0);

	COLORF cellColor; // Using only R, G, B
	float cellDensity;

	BYTE *rowPtr = (BYTE*)lockedRect.pBits;
	for (int y = 0; y < desc.TextureSize.y; y++)
	{
		COLOR *pixelPtr = (COLOR*)rowPtr;
		for (int x = 0; x < desc.TextureSize.x; x++)
		{
			const float *cellVal = (const float*)matrixCellPtr;

			// Determine cell density
			switch (desc.MatrixFormat)
			{
			case MATRIX_FORMAT_DENSITY:
				cellDensity = *cellVal;
				break;
			case MATRIX_FORMAT_COLOR_DENSITY:
				cellDensity = cellVal[1];
				break;
			case MATRIX_FORMAT_RGB_DENSITY:
				cellDensity = cellVal[3];
				break;
			default:
				assert(0);
				break;
			}

			if (cellDensity < FLT_EPSILON)
				cellColor = COLORF(0.f, 0.f, 0.f, 0.f);
			else
			{
				// Determine native cell color (RGB)
				switch (desc.MatrixFormat)
				{
				case MATRIX_FORMAT_DENSITY:
					cellColor.R = cellColor.G = cellColor.B = 1.f;
					break;
				case MATRIX_FORMAT_COLOR_DENSITY:
					cellColor.R = cellColor.G = cellColor.B = cellVal[0] / cellDensity;
					break;
				case MATRIX_FORMAT_RGB_DENSITY:
					{
						float densityInv = 1.f / cellDensity;
						cellColor.R = cellVal[0] * densityInv;
						cellColor.G = cellVal[1] * densityInv;
						cellColor.B = cellVal[2] * densityInv;
					}
					break;
				default:
					assert(0);
					break;
				}

				// Cell density as color alpha with tone mapping
				if (desc.RenderParams.LogColor)
					cellColor.A = logf(cellDensity) * toneMappingFactor;
				else
					cellColor.A = cellDensity * toneMappingFactor;
			}

			if (gradient)
			{
				float savedAlpha = cellColor.A;
				gradient->GetColorAt(cellColor, cellColor.R);
				cellColor.A = savedAlpha;
			}

			// Here cellColor has correct color and alpha proportional to cell density.

			if (gammaCorrection)
			{
				cellColor.R = powf(cellColor.R, gammaExponent);
				cellColor.G = powf(cellColor.G, gammaExponent);
				cellColor.B = powf(cellColor.B, gammaExponent);
			}

			cellColor.R *= desc.RenderParams.ColorScale.R;
			cellColor.G *= desc.RenderParams.ColorScale.G;
			cellColor.B *= desc.RenderParams.ColorScale.B;
			cellColor.A *= desc.RenderParams.ColorScale.A;

			if (useColorBias)
			{
				cellColor.R += desc.RenderParams.ColorBias.R;
				cellColor.G += desc.RenderParams.ColorBias.G;
				cellColor.B += desc.RenderParams.ColorBias.B;
				cellColor.A += desc.RenderParams.ColorBias.A;
			}

			if (!desc.RenderParams.TransparentBackground)
			{
				cellColor.R = Lerp(desc.RenderParams.BackgroundColor.R, cellColor.R, cellColor.A);
				cellColor.G = Lerp(desc.RenderParams.BackgroundColor.G, cellColor.G, cellColor.A);
				cellColor.B = Lerp(desc.RenderParams.BackgroundColor.B, cellColor.B, cellColor.A);
				cellColor.A = desc.RenderParams.BackgroundColor.A;
			}

			*pixelPtr = ColorfToColor(cellColor);
			
			matrixCellPtr += matrixCellSize;
			pixelPtr++;
		}
		rowPtr += lockedRect.Pitch;
	}

	texture->UnlockRect(0);
}

bool Screen::OnKeyDown( uint4 Key )
{
	switch (Key)
	{
	case 'F':
		ResetView();
		return true;
	default:
		return false;
	}
}

void Screen::MakeFractal( Desc &desc )
{
	desc.Preprocess();

	IDirect3DTexture9 *texture;
	ERR_GUARD_DIRECTX( frame::Dev->CreateTexture(
		(UINT)desc.TextureSize.x,
		(UINT)desc.TextureSize.y,
		1,
		0,
		D3DFMT_A8R8G8B8,
		D3DPOOL_MANAGED,
		&texture,
		NULL) );
	m_Texture.reset(texture);
	m_TextureSize = desc.TextureSize;

	shared_ptr<gradients::BaseGradient> gradient = LoadGradient(desc.RenderParams);

	/*{ // Debug - saving 1D texture
		COLORF arr[512];
		gradient->RenderAll(arr, 512);
		IDirect3DTexture9 *t;
		frame::Dev->CreateTexture(512, 1, 1, 0, D3DFMT_X8R8G8B8, D3DPOOL_SCRATCH, &t, NULL);
		D3DLOCKED_RECT lr;
		t->LockRect(0, &lr, NULL, 0);
		for (uint i = 0; i < 512; i++)
			((COLOR*)lr.pBits)[i] = ColorfToColor(arr[i]);
		t->UnlockRect(0);
		D3DXSaveTextureToFile("G:\\tmp\\debug.bmp", D3DXIFF_BMP, t, NULL);
		t->Release();
	}*/

	ValueMatrix valueMatrix(desc.MatrixFormat, (uint)(desc.TextureSize.x*desc.TextureSize.y));
	LOG(LOG_APP, "Calculating fractal...");
	GenerateValueArray(valueMatrix, desc);
	LOG(LOG_APP, "Rendering fractal...");
	RenderValuesToTexture(texture, valueMatrix, desc, gradient.get());
	LOG(LOG_APP, "Done.");
}

void Screen::OnFileChange( uint FileId )
{
	NewFractal();
}

void Screen::ProcessPoint_Fork( InternalContext &ctx, const Desc &desc, const VEC2 &pos, uint iterIndex, float iterIndex_f, const COLORF &color )
{
	ctx.IterationIndex = iterIndex;
	DrawPoint(ctx, desc, pos, color);

	if (iterIndex < desc.IterationCount)
	{
		iterIndex++;
		iterIndex_f += 1.f;
		VEC2 newPos;
		COLORF newColor;
		for (uint i = 0; i < desc.Transforms.size(); i++)
		{
			ctx.IterationIndex = iterIndex;
			TransformFunction &transform = *desc.Transforms[i].get();

			newPos = pos;
			newColor = color;
			transform.TransformPoint(newPos, ctx);
			if (transform.ProcessColor)
				TransformColor(newColor, transform, desc);

			ProcessPoint_Fork(ctx, desc, newPos, iterIndex, iterIndex_f, newColor);
		}
	}
}

float Screen::CalcToneMappingFactor(const Desc &desc, ValueMatrix &matrix)
{
	uint stride = MatrixFormatToPixelSize(desc.MatrixFormat);
	const BYTE *densityPtr = (const BYTE*)matrix.GetPtr();
	switch (desc.MatrixFormat)
	{
	case MATRIX_FORMAT_DENSITY:
		break;
	case MATRIX_FORMAT_COLOR_DENSITY:
		densityPtr += sizeof(float);
		break;
	case MATRIX_FORMAT_RGB_DENSITY:
		densityPtr += sizeof(float)*3;
		break;
	default:
		assert(0);
	}

	int pixelCount = desc.TextureSize.x * desc.TextureSize.y;
	float maxDensity = 0.f;
	for (int i = 0; i < pixelCount; i++)
	{
		if (*(const float*)densityPtr > maxDensity)
			maxDensity = *(const float*)densityPtr;
		densityPtr += stride;
	}
	if (maxDensity > 0.f)
	{
		if (desc.RenderParams.LogColor)
			return 1.f / logf(maxDensity);
		else
			return 1.f / maxDensity;
	}
	else
		return 0.f;
}

bool Screen::ProcessConsoleCommand( common::Tokenizer &tokenizer )
{
	if (tokenizer.QueryToken(Tokenizer::TOKEN_IDENTIFIER))
	{
		if (tokenizer.GetString() == "save")
		{
			if (m_Texture.is_null())
			{
				LOG(LOG_APP, "No texture.");
				return true;
			}

			tokenizer.Next();

			string fileName, filePath;
			if (tokenizer.QueryEOF())
			{
				if (!AskForSavePath(filePath))
					return true;
			}
			else if (tokenizer.QueryToken(Tokenizer::TOKEN_IDENTIFIER, Tokenizer::TOKEN_STRING))
			{
				fileName = tokenizer.GetString();
				tokenizer.Next();
				tokenizer.AssertEOF();

				RelativeToAbsolutePath(&filePath, frame::DataDir, fileName);
			}
			else
				tokenizer.CreateError();

			D3DXIMAGE_FILEFORMAT format;
			if (!GetTextureFileNameFormat(format, filePath))
				tokenizer.CreateError("Unrecognized texture file format.");

			LOG(LOG_APP, "Saving IFS Fractal texture to \"" + filePath + "\"...");
			ERR_GUARD_DIRECTX( D3DXSaveTextureToFile(
				filePath.c_str(),
				format,
				m_Texture.get(),
				NULL) );
			LOG(LOG_APP, "Done.");

			return true;
		}
	}

	return false;
}

bool Screen::AskForSavePath( string &outFilePath )
{
	return BrowseableFileType::ShowSaveDialog(outFilePath, GetTextureFileType());
}

shared_ptr<gradients::BaseGradient> Screen::LoadGradient( const RenderDesc &renderDesc )
{
	shared_ptr<gradients::BaseGradient> result;

	switch (renderDesc.GradientType)
	{
	case GRADIENT_NONE:
		break;
	case GRADIENT_TEXTURE:
		{
			gradients::TextureGradient *textureGradient = new gradients::TextureGradient();
			result.reset(textureGradient);
			textureGradient->Load(renderDesc.GradientFilePath);
		}
		break;
	case GRADIENT_APOPHYSIS:
		{
			gradients::ApophysisGradient *apophysisGradient = new gradients::ApophysisGradient();
			result.reset(apophysisGradient);
			apophysisGradient->Load(renderDesc.GradientFilePath, renderDesc.GradientName);
		}
		break;
	case GRADIENT_GIMP:
		{
			gradients::GimpGradient *gradient = new gradients::GimpGradient();
			result.reset(gradient);
			gradient->Load(renderDesc.GradientFilePath);
		}
		break;
	default:
		assert(0);
	}

	return result;
}

} // namespace IfsFractal
